'''
本模块是在线天气查询模块的一部分,
底层依赖模块有：city_json(提供城市名称代码转换)
本模块主要实现发送请求并对返回的json数据进行处理
'''
__author__ = 'lyx'
__version__ = '0.2'

import json
import os
import time
from urllib import request,error

import city_json  #对其中可能引发的异常仍不处理：留给再上一层模块

SAVEDIR = 'weather_data'  #存储缓存文件的目录名

#查询json数据的关键字
FORECAST = 'forecast'
DATA = 'data'


class Weather(object):
    '''用于发起网络请求查询天气的类，同时进行缓存'''
    def __init__(self,city,autosave=True):
        '''city为城市名称
            autosave表示是否进行自动缓存（默认为缓存)
            默认情况下如果有缓存会读取,如要强行刷新可以调用flush方法来刷新
        '''
        self.city_name = city   
        self.city_code = list(city_json.name_code(city).values())[0]

        if not self.city_code:
            raise ValueError(f'区域{city}不存在或范围过大')
                              
        self._filename = SAVEDIR+'/'+self.city_name+'_forecast.json'  #缓存文件的路径
        self._histname = SAVEDIR+'/'+self.city_name+'_histroy.json'  #储存历史文件的路径

        #不存在文件,请求并下载
        if not os.path.isfile(self._filename):
            self.flush()
            if autosave:
                self.save()
        else:
            self._loadfile()  #存在缓存文件则读取

    def flush(self):
        '''强行刷新城市的天气数据,但不会返回或者缓存结果
        结果可以通过调用属性查看
        '''
        response = request.urlopen('http://t.weather.itboy.net/api/weather/city/'
                                   +self.city_code)
        self._data = json.loads(response.read().decode('utf-8'))


    def _loadfile(self):
        '''内部函数,用于从本地文件中加载缓存。
            尽量不要直接调用：如果不存在文件会直接报错
        '''
        #判断文件创建时间：每1小时强行刷新一次
        if int(time.time())-int(os.path.getctime(self._filename)) >= 3600:
            os.remove(self._filename)
            self.flush()
            self.save()
            return None
        
        with open(self._filename,encoding = 'utf-8') as f:
            try:
                self._data = json.load(f)
            except json.JSONDecodeError:
                f.close()

                #重新下载天气数据并缓存
                os.remove(self.filename)
                self.flush()
                self.save()


    def save(self):
        '''存储当前的天气数据,
            如果已存在数据将会覆盖
        '''
        if not os.path.exists(SAVEDIR):
            os.mkdir(SAVEDIR)

        with open(self._filename,'w',encoding='utf-8') as f:
            json.dump(self._data,f,ensure_ascii = False,indent=4)

    def save_as_histroy(self):
        '''在天气历史记录中记录下该天,一般为Weather_GUI调用'''
        if not os.path.exists(SAVEDIR):
            os.mkdir(SAVEDIR)

        short_data = {self._data[DATA][FORECAST][0]['ymd']:{key:value for key,value in self._data[DATA][FORECAST][0].items()
                                  if key in ['ymd','high','low','type','sunrise','sunset']}}
        histroy = {}

        if os.path.isfile(self._histname):
            with open(self._histname,'r',encoding='utf-8') as f:
                try:
                    histroy = json.load(f)
                except json.JSONDecodeError:
                    f.close()
                    os.remove(self._histname)
       
        histroy.update(short_data)

        with open(self._histname,'w',encoding='utf-8') as f:
            json.dump(histroy,f,ensure_ascii= False,indent=4)

    def load_histroy(self,day=None):
        '''读取天气历史记录,day为一个ISO格式日期
        若不输入day,返回一个字典,键为有记录当天的ISO格式日期,值为当天部分数据
        输入day返回那天的数据,不存在数据则抛出ValueError
        不存在文件原样抛出FileNotFoundError
        '''
        with open(self._histname,'r',encoding='utf-8') as f:
            try:
                histroy = json.load(f)
            except json.JSONDecodeError:
                raise FileNotFoundError('历史数据已损坏！') from None

        if day is not None:
            try:
                result = histroy[day]
            except KeyError:
                raise ValueError(f'{day}不存在数据!') from None
            else:
                return result

        return histroy
            
            
    def get_data(self,key,default=None):
        '''从返回的数据中取得需要的属性
            如要取得15天预报信息,请使用forecast方法
            如果不存在该属性,返回default的值,
            如果不填写default,将原样抛出KeyError
        '''
        if key == 'forecast':
            raise ValueError('无法使用此方法查看预报^请使用forecast方法')

        try:
            ans = self._data[key]
        except KeyError:
            if default is not None:
                return default
            else:raise

        return ans

    def forecast(self,date):
        '''返回15天内某天的预报。
            date可以为一个ISO日期（year-month-date)或一个包含（年，月，日）的元组
            也可以是该日相对今天有几天（例：今天为0，明天则为1）
            e.g.以下格式可以接受：
                2021-1-22;(2021,1,22);1(假设今天是2021年1月21日）
            如果不存在该日期的预报,将抛出ValueError
        '''
        if isinstance(date,tuple):
            if len(date) == 3:
                date = [str(one) for one in date]
                date = '-'.join(date)
            else:
                raise ValueError('日期格式不正确！')

        #如果date是ISO日期格式
        if '-' in str(date):
            date = date.split('-')
            for index,one in enumerate(date):
                if len(one) == 1:
                    date[index] = '0'+one
            date = '-'.join(date)
                    
            for one in self._data['data'][FORECAST]:
                if one['ymd'] == date:
                    return one

            raise ValueError(f'{date}：日期不存在')  #如果没找到结果抛出异常

        #处理输入相对日期的情况
        else:
            try:
                date = int(date)
            except ValueError:
                raise ValueError(f'不支持这个格式的日期:{date}') from None

            try:
                ans = self._data['data'][FORECAST][date]
            except IndexError:
                raise ValueError(f'{date}：日期不存在') from None
            else:
                return ans
                
            
def _main():
    def print_day(date,head = True):
        forecast = wea.forecast(date)
        print()  #空行
        if head:
            print('您正在查询日期 ：',forecast['ymd'])
            
        for desc,addr in zip(['星期','最高温','最低温','天气',
                              '日出','日落','风向','风力','空气质量指数(AQI)','温馨提示'],
                             ['week','high','low','type',
                              'sunrise','sunset','fx','fl','aqi','notice']):
            print('    '+desc+' ：',forecast[addr])
                  
    print('Weather(1.1.0)')
    name = ''
    while not name:
        cho = input('请输入一个城市查询天气： ')
        name = city_json.name_code(cho,partial=True)
        
    names = list(city_json.name_code(cho,True).keys())
    if len(names) != 1:
        print('搜索到了多个区域：\n')
        for one in names:
            print('    '+one)

        cho = ''
        while cho not in names:
            if cho:
                print('选择的区域不存在！')
            cho = input('\n请输入您要查询的区域： ')

    wea = Weather(cho)
    cityInfo = wea._data['cityInfo']
    data = wea._data['data']

    print(f'\n已获取到{cityInfo["city"]}的天气数据,更新于{cityInfo["updateTime"]}\n')
    for description,addr in zip(['湿度','PM2.5','PM10','空气质量','出行建议'],
                                ['shidu','pm25','pm10','quality','ganmao']):
        print('    '+description+' ：',data[addr])
    print_day(0,head = False)
        
    print('\n输入一个日期（year-month-date)来查看当天预报（限十五天内)')

    while True:
        cho = input('\n请输入：')
        if not cho:break
        try:
            print_day(cho)
        except ValueError as e:
            print(str(e))
            

if __name__ == '__main__':
   _main()
        
        
